/*
 * Copyright (c) 2009 Nathaniel Houghton <nathan@brainwerk.org>
 *                    Robert Kirchgessner <rkirchge@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose with or without fee is hereby granted, provided that
 * the above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>

#include <util/delay.h>

#include "usart.h"
#include "elm.h" 
#include "obd_pid.h"
#include "elm_dtc.h"
#include "util.h"
#include "ui.h"

#define DTC_PAIRS_PER_RESPONSE 3

#define MAX_DTC_CODES 127

static uint16_t dtc_codes[MAX_DTC_CODES];
static uint8_t curr;
static uint8_t milval; /*Value returned from PID01*/

uint16_t elm_dtc_get( uint8_t reset ); /*Get the actual DTCs*/
uint8_t elm_dtc_peek(); /*Asks the car for PID01*/
uint16_t elm_dtc_get_single(uint8_t reset);
uint16_t elm_dtc_get_multi(uint8_t reset);


uint8_t 	
elm_dtc_count() /*Gets the number of trouble codes detected*/
{
	return (milval & DTC_COUNT_MSK);
}

uint8_t		
elm_dtc_mil() /*Gets whether the check engine light is on */
{
	return (milval & CHECK_ENGINE_MSK);
}

uint8_t 	
elm_dtc_peek() /*Gets the number of trouble codes detected, and sets check_engine to 1 if the check enchne light is on*/
{
	uint8_t buf[4]; 
	
	elm_getpid( STATUS_MILDTC, buf, sizeof(buf) ); /*Get the MIL/DTC code*/
	milval = buf[0];
	
	return buf[0]; /*Return the first byte. MSB is the check engine light flag. The rest is the #DTC codes.*/
}

void
elm_dtc_update() /*Get new DTC list from the car*/
{
	char buf[] = "03\r"; /*Mode 3 gets the trouble codes*/
	uint8_t i;
	uint8_t num_dtc; /*Current number of DTC*/
	
	elm_dtc_peek(); /*Set the milval variable through a mode 1 request*/
	
	num_dtc = elm_dtc_count(); /*Get the number of DTCs*/
	
	elm_dtc_get(1); /*Reset the counter, doesnt grab any data. Just resets a static variable*/
	
	usart_puts( DEV_ELM, buf ); /*Send the MODE03 request to get the DTC*/
	
	for( i=0; i<MAX_DTC_CODES && i<num_dtc; ++i ) /*Fill out the static DTC array*/
	{
		dtc_codes[i] = elm_dtc_get(0); /*Get the next DTC*/
		
		if(dtc_codes[i] == 0 ) /*Multiline response fail*/
			break;
	}

	curr = 0; /*Reset the current position*/

	elm_flush('>');
	
	return;
}

uint16_t
elm_dtc_next() /*Get the next dtc*/
{
	uint8_t num_dtc;
	
	num_dtc = elm_dtc_count();
	
	if( curr == num_dtc-1 ) /*If we are at the end, goto the beginning*/
		curr = 0;
	else
		curr = curr+1; /*Otherwise just move to the next element*/
	
	return dtc_codes[curr];
}

uint16_t	
elm_dtc_current() /*Get the current dtc*/
{
	return dtc_codes[curr];
}

uint16_t
elm_dtc_prev() /*Get previous dtc*/
{
	uint8_t num_dtc;
	
	num_dtc = elm_dtc_count();
	
	if( curr == 0 ) /*If we are at the end, goto the beginning*/
		curr = num_dtc-1;
	else
		curr = curr-1; /*Otherwise just move to the next element*/
	
	return dtc_codes[curr];
}

void	  	
elm_dtc_clear() /*Clear the trouble codes*/
{
	char buf[] = "04\r";

	/*Need to do some cleanup for the DRC structure here */
	
	/*Clearing the trouble codes is as easy as sending a MODE04 request*/ 
	usart_puts( DEV_ELM, buf ); /*Send the MODE04 request to clear the DTC*/

	elm_flush('>'); /*Find the prompt*/
}

uint16_t 
elm_dtc_get( uint8_t reset )  /*Get the actual DTCs*/
{
	uint16_t r;
	
	if( PROTCAN(myelm.prot) && elm_dtc_count() > 2 )
		r = elm_dtc_get_multi(reset);
	else
		r = elm_dtc_get_single(reset);
	
	return r;
}

uint16_t
elm_dtc_get_single(uint8_t reset)
{
	uint8_t buf[2];
	static uint8_t cnt; /*Keep track of how many DTC we read, so we know when to expect the next tag.*/
	static uint8_t resp_len;
	
	if( reset == 1 )
	{
		cnt = 0;
		return 0;
	}

	if(cnt == 0 )
	{
		if( PROTCAN(myelm.prot) )
		{
			elm_readdata( buf, 2 ); /*Read the DTC response bytes */
			resp_len = buf[1]; /*The second byte returned (for CAN) is the count of DTCs on that line*/
		}
		else
		{
			elm_readdata( buf, 1 ); /*Read the DTC response byte */ 
			resp_len = DTC_PAIRS_PER_RESPONSE; /*Non-can cars have 3 responses per line..*/
		}
	}
	
	elm_readdata( buf, 2 ); /*Read the DTC in 2 byte pairs until we get the one we want */
	cnt++; /*Increment the DTC byte pair counter*/
		
	if( cnt == resp_len ) /*End of the current DTC line*/
	{
		cnt = 0;
	}	

	return buf[1]<<8| buf[0];
}

uint8_t btoh(uint8_t *, char *, uint8_t);

uint8_t 
multi_linenum()
{
	char buf[4];
	elm_gets(buf, 4); /*Should get the line number, 1 hex digit, a colon and a space.*/

	buf[0] = toupper(buf[0]);

	hexdump(buf, 3);
	
	if(buf[1] != ':') {
		_delay_ms(800);
		fatal("OMG NO COLON");
	}
		
	if(buf[0]>='A' && buf[0]<='F')
		return buf[0]-'A' + 0x10;
	else
		return buf[0]-'0';
	
} 

#define MULTI_LINE_PACKET_LENGTH 7

uint16_t
elm_dtc_get_multi(uint8_t reset)
{
	static uint16_t line_count;
	static uint8_t cnt;
	
	uint16_t code;
	uint8_t i;
	uint8_t state;

	uint16_t line;
	
	uint8_t buf[32];
	
	if(reset == 1) {
		line_count=0;
		cnt=0;
		return 0;
	}
	
	if(line_count == 0 && cnt == 0) {
		elm_readdata(buf,sizeof(buf)); /*This will get the multi-line byte cound (3 hex digts???? WHY did they do that?)*/
#if 0		
		num_lines = buf[0]&0xF<<4)|(buf[1]>>4);
		num_lines += 1;
		num_lines = ceil( num_lines / 7.0 ); 
		snprintf(buf,sizeof(buf), "Number of lines: %d", num_lines);
		usart_puts(0,buf);
#endif
		cnt++; /*Counts as first byte on first line..*/
	}
	
	code = 0;
	state = 0;
	
	for(i=0;i<2;++i)
	{
		usart_puts(0, "loopdyloop\r\n");
		if( (line_count == 0 && cnt == 1) || (cnt==0 && line_count != 0) )
		{
			line = multi_linenum();
			
			if(line != line_count%16 )
			{
				lcd_update(&lcd, 1,  "Bus Error. Retry." );
				_delay_ms(300);
				menu_set_current( MENU_ID_ROOT );
				menu_repaint();
				return 0;
			}
		}
		
		if(line_count == 0 && cnt == 1)
		{
			cnt+=2;
			elm_readdata(buf,2); /*Throw away the response bytes*/
		}
			
		elm_readdata(buf,1);
		cnt++;
		
		if(state == 0){
			code = buf[0];
			state = 1;
		}
		else {
			code |= buf[0]<<8;
			state = 0;
		}
		
		if( cnt == MULTI_LINE_PACKET_LENGTH )
		{
			elm_flush('\n');
			line_count++;
			cnt = 0;
		}
	}
	
	return code;
}
